package com.jcroke.PC32.basetransform;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.Set;

import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.CompilerFactoryFactory;
import org.codehaus.commons.compiler.IScriptEvaluator;

import com.jcroke.DACGraph.Pnode;
import com.jcroke.DACGraph.Signature;
import com.jcroke.PC32.datum.Datum;
import com.jcroke.PC32.exception.InjectorException;
import com.jcroke.PC32.exception.TransformerException;

/**
 * Parent class of all code-injection transformers.
 * 
 * @author jcroke
 */

public final class Injector extends Transformer {

	public void prepare(Pnode pnode) {
		this._pnode = pnode;
		System.out.println("[Prepare] Injector runnning prepare on: "
				+ _pnode.getNodeName());
		super.declareInputFields();

		try {
			this._iE = CompilerFactoryFactory.getDefaultCompilerFactory()
					.newScriptEvaluator();
		} catch (Exception e) {
			throw new TransformerException(
					"Injector transformer failed to create newScriptEvaluator object",
					e);
		}

		if (_pnode.getLogic().hasImport()) {
			String[] imps = Pnode.substrIRI(
					_pnode.getLogic().getSourceRDFImport()).split("\\s*,\\s*");
			_iE.setDefaultImports(imps);
		}

		if (_pnode.getLogic().hasThrows()) {
			String[] thrs = Pnode.substrIRI(
					_pnode.getLogic().getSourceRDFThrows()).split("\\s*,\\s*");
			Class<?>[] thrsClasses = new Class<?>[thrs.length];
			for (int i = 0; i < thrsClasses.length; i++) {
				try {
					thrsClasses[i] = Class.forName(thrs[i]);
				} catch (ClassNotFoundException e) {
					throw new InjectorException(
							"Code injector cannot find an Exception type class for name: ["
									+ thrs[i] + "]", e);
				}
			}
			_iE.setThrownExceptions(thrsClasses);
		}

		// Injected code can only return 1 object to memory.
		ArrayList<Signature> outputSig = _pnode.getOutputSignature();
		if (outputSig.size() != 1) {
			throw new InjectorException(
					"Injector found more than one output signature for pnode: "
							+ pnode.getNodeName());
		}
		String singleOutputSig = outputSig.get(0).getSourceTypeClass();

		try {
			_iE.setReturnType(Class.forName(singleOutputSig));
		} catch (ClassNotFoundException e) {
			throw new InjectorException(
					"Code injector cannot find Return type class for name: "
							+ singleOutputSig, e);
		}

		ArrayList<Signature> inputSigs = _pnode.getInputSignature();
		int size = inputSigs.size();

		Class<?>[] inputParamTypes = new Class<?>[size];
		String[] inputParamNames = new String[size];

		int i = 0;
		for (Signature s : inputSigs) {
			inputParamNames[i] = s.getLocalPin();
			try {
				inputParamTypes[i++] = Class.forName(s.getSourceTypeClass());
			} catch (ClassNotFoundException e) {
				throw new InjectorException(
						"Code injector cannot find Input Parameter type class for name: ",
						e);
			}
		}
		_iE.setParameters(inputParamNames, inputParamTypes);
		
		Reader script = new StringReader(Pnode.substrIRI(pNode().getLogic().getSourceRDFCode()));
		try {
			_iE.cook(script);
		} catch (CompileException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public LinkedHashMap<String, Datum> receive() {
		return (_iBuf);
	}

	public void execute() {
		LinkedHashMap<String, Datum> args = receive();
		Hashtable<String, Datum> dOut = new Hashtable<String, Datum>();
		String tag = pNode().getOutputSignature().get(0).getSourcePin();
		Datum[] dArgs = new Datum[args.size()];
		int i = 0;
		
		Set<String> keys = args.keySet();
		for (String k : keys) {
			dArgs[i++] = args.get(k);
		}
		Datum ret = null;
		try {
			ret = (Datum) _iE.evaluate(dArgs);
		} catch (InvocationTargetException e) {
			throw new InjectorException("Injector failed to execute code for: "
					+ _pnode.getNodeName(), e);
		}
		dOut.put(tag, ret);
			emit(dOut);
	}

	private IScriptEvaluator _iE;
}
